using System.Collections.ObjectModel;
using System.Net.WebSockets;
using System.Security.Claims;
using System.Text;
using Crux.Core.Websockets.Metadata;
using Microsoft.AspNetCore.Http;

namespace Crux.Core.Websockets;
/// <summary>
/// WebSocketConnectionContext
/// </summary>
public class WebSocketConnectionContext : IDisposableObservable
{
    #region private fields
    /// <summary>
    /// 连接id
    /// </summary>
    public string ConnectionId { get; init; } = $"{Guid.NewGuid():N}";
    /// <summary>
    /// 用户identifier
    /// </summary>
    public string? UserIdentifier => this.HttpContext?.User?.Identity?.Name;
    /// <summary>
    /// 用户
    /// </summary>
    public ClaimsPrincipal User => this.HttpContext.User;
    /// <summary>
    /// 取消标志
    /// </summary>
    public CancellationToken ConnectionAborted => this.HttpContext.RequestAborted;
    /// <summary>
    /// 已加入的group
    /// </summary>
    /// <returns></returns>
    public ReadOnlyCollection<string> Groups => new ReadOnlyCollection<string>(InternalGroups.ToList());
    /// <summary>
    /// 查询
    /// </summary>
    public IQueryCollection? Query => HttpContext.Request?.Query;
    /// <summary>
    /// http上下文
    /// </summary>
    internal HttpContext HttpContext { get; init; }
    /// <summary>
    /// websocket连接字符串
    /// </summary>
    internal WebSocket WebSocket { get; init; }
    /// <summary>
    /// options
    /// </summary>
    internal WebSocketOptions Options { get; init; }
    /// <summary>
    ///
    /// </summary>
    private readonly ILogger<WebSocketConnectionContext>? _logger;
    /// <summary>
    /// 加入了那些组
    /// </summary>
    /// <returns></returns>
    public HashSet<string> InternalGroups { get; init; } = new HashSet<string>();
    /// <summary>
    ///
    /// </summary>
    private readonly ConcurrentQueue<byte[]> messages = new();
    /// <summary>
    ///
    /// </summary>
    private int _location;
    #endregion  private fields
    #region .ctor
    /// <summary>
    /// .ctor
    /// </summary>
    /// <param name="context"></param>
    /// <param name="socket"></param>
    /// <param name="options"></param>
    internal WebSocketConnectionContext(
        HttpContext context,
        WebSocket socket,
        WebSocketOptions options)
    {
        HttpContext = context
            ?? throw new ArgumentNullException(nameof(context));
        WebSocket = socket
            ?? throw new ArgumentNullException(nameof(socket));
        Options = options
            ?? throw new ArgumentNullException(nameof(options));

        _logger = context.RequestServices.GetService(typeof(ILogger<WebSocketConnectionContext>)) as ILogger<WebSocketConnectionContext>;

        ConnectionAborted.Register(() =>
        {
            Dispose();
        });
    }
    #endregion


    #region methods
    /// <summary>
    /// 中断连接
    /// </summary>
    public void Abort()
    {
        if (!(HttpContext?.WebSockets?.IsWebSocketRequest ?? false)) return;

        WebSocket.Abort();
    }
    /// <summary>
    /// 发送信息
    /// </summary>
    /// <param name="message"></param>
    /// <returns></returns>
    public async Task SendAsync(Message message)
    {
        if (WebSocketState.Open != WebSocket.State ||
            ConnectionAborted.IsCancellationRequested) return;

        var json = message.JsonStringify();

        var buffer = Encoding.UTF8.GetBytes(json);

        messages.Enqueue(buffer);

        await InternalSendAsync().ConfigureAwait(false);
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="buffer"></param>
    /// <returns></returns>
    public async Task SendAsync(byte[] buffer)
    {
        if (WebSocketState.Open != WebSocket.State ||
            ConnectionAborted.IsCancellationRequested) return;

        messages.Enqueue(buffer);

        await InternalSendAsync().ConfigureAwait(false);
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="messageType"></param>
    /// <param name="endOfMessage"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task<bool> SendAsync(
        ArraySegment<byte> buffer,
        WebSocketMessageType messageType,
        bool endOfMessage,
        CancellationToken cancellationToken = default)
    {
        if (Interlocked.CompareExchange(ref _location, 1, 0) != 0)
        {
            return false;
        }

        try
        {
            using var source = CancellationTokenSource.CreateLinkedTokenSource(ConnectionAborted, cancellationToken);

            await WebSocket.SendAsync(buffer, messageType, endOfMessage, cancellationToken).ConfigureAwait(false);
        }
        catch (ObjectDisposedException)
        {

        }
        catch (OperationCanceledException)
        {

        }
        catch (Exception e)
        {
            _logger?.LogError(e, e.Message);
        }
        finally
        {
            Interlocked.Exchange(ref _location, 0);
        }

        return true;
    }
    private async Task InternalSendAsync()
    {
        if (Interlocked.CompareExchange(ref _location, 1, 0) != 0)
        {
            return;
        }

        try
        {
            while (messages.TryDequeue(out var message))
            {
                if (message is null || 0 == message.Length)
                {
                    continue;
                }

                var buffer = new ArraySegment<byte>(message);

                await WebSocket.SendAsync(
                    buffer: buffer,
                    messageType: WebSocketMessageType.Text,
                    endOfMessage: true,
                    cancellationToken: ConnectionAborted).ConfigureAwait(false);

                Array.Clear(message, 0, message.Length);

                message = null;
            }
        }
        catch (ObjectDisposedException)
        {

        }
        catch (OperationCanceledException)
        {

        }
        catch (Exception e)
        {
            _logger?.LogError(e, e.Message);
        }
        finally
        {
            Interlocked.Exchange(ref _location, 0);
        }
    }
    /// <summary>
    /// 
    /// </summary>
    public bool IsDisposed { get; private set; }

    /// <summary>
    /// 释放资源
    /// </summary>
    public void Dispose()
    {
        if (IsDisposed) return;

        IsDisposed = true;
        GC.SuppressFinalize(this);

        InternalGroups.Clear();
    }
    #endregion
    /// <summary>
    ///
    /// </summary>
    /// <param name="context"></param>
    /// <param name="socket"></param>
    /// <param name="options"></param>
    /// <returns></returns>
    public static WebSocketConnectionContext Create(HttpContext context, WebSocket socket, WebSocketOptions options)
        => new WebSocketConnectionContext(context, socket, options);
}